/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package stockcontrol;

import entidades.Categoria;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.List;
import java.util.Vector;
import javax.persistence.EntityManager;

public class Estadisticas {

    private EntityManager em;

    private List meses = new Vector();
    private List anios = new Vector();

    private List dataVentas = new Vector();

    private int categoria;

    private double[] rangos;
    private Vector<Double> v_estrella = new Vector<Double>();

    private Vector datosConvergencia = new Vector();

    public Estadisticas(int nroCategoria, List meses, List anios, EntityManager em){
        this.meses = meses;
        this.anios = anios;
        this.categoria = nroCategoria;
        dataVentas.addAll(getCantArtMes(nroCategoria));
        this.em = em;
    }

    public Estadisticas(int nroCategoria, EntityManager em){
        this.em = em;
        dataVentas.addAll(getCantArtMes(nroCategoria));
        this.categoria = nroCategoria;
        rangos = this.getRangos(dataVentas, 3);
    }

    public int getCategoria() {
        return categoria;
    }

    public void setCategoria(Categoria categoria) {
        this.categoria = categoria.getCodigo();
        this.dataVentas = this.getCantArtMes(this.categoria);
    }

    public List getAnios() {
        return anios;
    }

    public void setAnios(List anios) {
        this.anios = anios;
    }

    public List getMeses() {
        return meses;
    }

    public void setMeses(List meses) {
        this.meses = meses;
    }

    public void setCantRangos(int n){
        this.rangos = getRangos( this.getDataVentas(), n);
    }

    public double[] getRangos(){
        return this.rangos;
    }

    public List getCantArtMes(int nroCategoria){
        String findCategoriaMes = "SELECT month(v.fecha), year(v.fecha), SUM(v.cantidad) " +
                "FROM Venta v LEFT JOIN Articulo a " +
                "ON v.articulo = a.codigo " +
                "GROUP BY month(fecha), year(fecha), codigo_cat " +
                "HAVING codigo_cat = :categoria " +
                "ORDER BY year(fecha), month(fecha) ASC";

        return em.createNativeQuery(findCategoriaMes).setParameter("categoria", nroCategoria).getResultList();
    }

    private double[] getRangos(List l, int N){
        double[] rango = new double[N];
        // inicializo
        for (int i = 0; i < rango.length; i++)
            rango[i] = 0;
        if ( l.isEmpty() )
            return rango;
        // maximo y minimo
        Object[] tupla = ( Object[] ) l.get(0);
        Integer max = (Integer) tupla[2];
        Integer min = (Integer) tupla[2];
        for (int i = 1; i < l.size(); i++) {
            tupla = ( Object[] ) l.get(i);
            Integer actual = (Integer) tupla[2];
            if (actual.compareTo(max) > 0)
                max = actual;
            if (actual.compareTo(min) < 0)
                min = actual;
        }

        rango[0] = (double)max / N;
        for (int i = 1; i < rango.length-1; i++) {
            rango[i] = (double) max / N + rango[i-1];
        }
        rango[rango.length-1] = max;

        return rango;
    }

    private Vector getEtiquetas(List l, double[] rango) {
        //System.out.println("size de l: " + l.size());
        Vector tags = new Vector(l.size());
        for (int i = 0; i < l.size(); i++) {
            Object[] tupla = ( Object[] ) l.get(i);
            int cant = (Integer) tupla[2];
            int pos = 0;
            for (int j = rango.length-1; j >= 0  ; j--)
                if ( cant <= rango[j] )
                     pos = j;
            tags.insertElementAt(pos,i);
        }

        return tags;
    }

    private double[][] calcularProbs(List l, Vector tags, double[] rango ){
        int[][] m = new int[rango.length][rango.length];
        
        //cuenta el pasaje de rango
        int[] cont = new int[rango.length];

        //inicializo en cero
        for (int i = 0; i < rango.length; i++) {
            cont[i] = 0;
            for (int j = 0; j < rango.length; j++) {
                m[i][j] = 0;
            }
        }

        //cuento
        int x = 0;
        int y = 0;

        for (int i = 1; i < tags.size(); i++) {
             x = (Integer)tags.get(i-1);
             y = (Integer)tags.get(i);
             cont[x]++;
             m[x][y]++;
        }
        cont[y]++;

        System.out.println("probabilidades:");
        double[][] probs = new double[rango.length][rango.length];
        for (int i = 0; i < rango.length; i++) {
            for (int j = 0; j < rango.length; j++) {
                if ( cont[i] != 0 )
                   probs[i][j] = (double)m[i][j]/cont[i];
                System.out.printf("%.3f ", probs[i][j]);
            }
            System.out.println( );
        }
        return probs;
    }

    private Vector<Double> montecarlo(double[][] matriz_acum) {
        //N estados posibles
        datosConvergencia.clear();
        v_estrella.clear();
        int N = matriz_acum.length;

        int[] exitos = new int[N];
        Vector<Double> prob_ant = new Vector<Double>(N);
        Vector<Double> prob_act = new Vector<Double>(N);

        boolean iterar = true;

        for (int i=0; i<N; i++)
            if ((matriz_acum[i][N-1] < 0.95) || (matriz_acum[i][N-1] > 1.05)){
                iterar = false;
                break;
         }

        for (int i = 0; i < N; i++) {
            exitos[i] = 0;
            prob_ant.add(i, -1.0);
            prob_act.add(i, 0.0);
        }

        //acumular v0 (equiprobable)
        double[] v0_acum = new double[N];
        System.out.print("equiprobable: ");
        v0_acum[0] = (double)1 / v0_acum.length;
        for (int i = 1; i < v0_acum.length; i++) {
            v0_acum[i] = (double)1 / v0_acum.length + v0_acum[i-1];
            System.out.print(v0_acum[i] + ", ");
        }

        int pasos = 1; //Contador que lleva la cantidad de pasos en total
        //se simula el primer paso de la trayectoria
        int x = primerPaso(v0_acum);
        //sumo un exito para el paso dado
        exitos[x]++;

        while ( pasos < 1000 ){
            x = sigDadoAnt(matriz_acum, x); //simulo el siguiente paso dado el anterior
            exitos[x]++;
            pasos++;
            prob_ant.clear();
            prob_ant.addAll(prob_act);
            for ( int i = 0; i < exitos.length; i++)
                prob_act.set(i, (double)exitos[i]/(double)pasos);
            datosConvergencia.add(prob_act.clone());
        }

        if (iterar) {
            while ( !converge(prob_ant, prob_act) ){
                x = sigDadoAnt(matriz_acum, x);
                exitos[x]++;
                pasos++;
                prob_ant.clear();
                prob_ant.addAll(prob_act);
                for ( int i = 0; i < exitos.length; i++)
                    prob_act.set(i, (double)exitos[i]/(double)pasos);
                if (datosConvergencia.size()<10000)
                    datosConvergencia.add(prob_act.clone());
            }
        } else {
            System.out.println("\nAtencion: las columnas no suman uno!!");
        }
        System.out.println("cant de pasos = " + pasos);

        return prob_act;
    }

    private boolean converge(Vector<Double> probAnt, Vector<Double> probAct)
    {
        final double EPSILON = 0.000001;
        for(int i=0; i < probAct.size(); i++)
            if ( Math.abs( probAnt.elementAt(i).doubleValue() - probAct.elementAt(i).doubleValue()) > EPSILON )
                return false;
        return true;
    }
    
    private int primerPaso(double[] v0){ //v0 es el vector inicial pasado por parametro
        double r = Math.random(); //se obtiene un valor aleatorio entre 0 y 1
        for(int i = 0; i < v0.length; i++)
            if( r <= v0[i] ) // Se chequea a qué rango pertenece ese valor generado
                return i;
        //System.out.println("salida no encontrada para (primer paso) " + r);
        return v0.length - 1;
    }
     

    /* siguiente dado el anterior */
    private int sigDadoAnt(double[][] matriz_acum, int col) { //los parametros requeridos son la matriz acumulada y el numero de columna que representa el paso anterior
        double r = Math.random(); //se obtiene un valor aleatorio entre 0 y 1
        int n = matriz_acum.length;
        for(int i=0; i< n; i++)
            if ( r <= matriz_acum[col][i] ){ // Se chequea a qué esquina pertenece ese valor generado dada la columna
                return i;
            }
        //System.out.println("salida no encontrada para (sig dado ant) " + r);
        return n-1;
    }

    public Vector<Double> calcularDistribucionDemanda(){
        //if (v_estrella.isEmpty()){
            Vector v = getEtiquetas(this.getDataVentas(), this.getRangos());
            double[][] p = calcularProbs(this.getDataVentas(), v, this.getRangos());
            //acumulada
            double[] acum = new double[rangos.length];
            for (int j = 0; j < rangos.length; j++)
                acum[j]=0;
            for (int i = 0; i < rangos.length; i++) {
                for (int j = 0; j < rangos.length; j++) {
                     acum[i] += p[i][j];
                     p[i][j] = acum[i];
                }
            }

            // calculo el v* con montecarlo
            v_estrella = montecarlo(p);
        //}

            return v_estrella;
    }

    public double[] getDemandaEstimada(){
        double[] r = this.getRangos();
        Vector<Double> distro = this.calcularDistribucionDemanda();
        int max = 0;
        for (int i = 0; i < distro.size(); i++) {
            double f = distro.elementAt(i);
            if ( f > distro.elementAt(max) )
                max = i;
        }
        double[] resultado = {0,0};
        if (max != 0)
            resultado[0] = r[max-1];
        resultado[1] = r[max];
        return resultado;
    }

   private List filtrarData(List data){
        List newData = new Vector();
        if ((meses.size() > 0) || (anios.size()>0)){
            for (int i = 0; i < data.size(); i++) {
                Object[] tupla = (Object[])data.get(i);
                if ( meses.contains((Integer)tupla[0]) && anios.contains((Integer)tupla[1]) )
                    newData.add(tupla);
            }
            return newData;
        }
        else
            return data;
    }

   public List getDataVentas(){
       if ( getMeses().isEmpty() || getAnios().isEmpty() )
           return dataVentas;
       else
           return filtrarData(this.dataVentas);
   }

   Vector getDatosConvergencia(){
        return this.datosConvergencia;
   }

   void salvarDatosConvergencia(){
       try{
        // Create file
            FileWriter fstream = new FileWriter("out.txt");
            BufferedWriter out = new BufferedWriter(fstream);
            for (int i = 0; i < datosConvergencia.size(); i++) {
               double[] fs = (double[]) datosConvergencia.elementAt(i);
               String s = new String();
               for (int j = 0; j < fs.length; j++) {
                    double f = fs[j];
                    s += f + " ";
               }
               out.write(s + "\n");
           }

            //Close the output stream
            out.close();
        }catch (Exception e){//Catch exception if any
          System.err.println("Error: " + e.getMessage());
        }
   }

   void salvarMatriz(double[][] m){
        try{
        // Create file
            FileWriter fstream = new FileWriter("out-matriz.txt");
            BufferedWriter out = new BufferedWriter(fstream);
            for (int i = 0; i < m.length; i++) {
               String s = new String();
               for (int j = 0; j < m.length; j++) {
                    double f = m[i][j];
                    s += f + " ";
               }
               out.write(s + "\n");
           }

        //Close the output stream
        out.close();
        }catch (Exception e){//Catch exception if any
          System.err.println("Error: " + e.getMessage());
        }

   }

   /*
    * Calculos para lote
    */

   private List getImporteExtraProm(){
        String findImporteExtraProm = "SELECT MONTH(c.fecha), YEAR(c.fecha), AVG(c.importe), AVG(c.extra) " +
                "FROM Compra c " +
                "WHERE c.codigo_cat=:categoria " +
                "GROUP BY month(c.fecha), year(c.fecha)";
        return em.createNativeQuery(findImporteExtraProm).setParameter("categoria", categoria).getResultList();
    }

   private float getPromedio(List l, int columna){
        float prom = 0;
        int i;
        for (i = 0; i < l.size(); i++) {
            Object[] tupla = (Object[])l.get(i);
            prom+=Float.parseFloat(tupla[columna].toString());
        }
        if (i!=0){
            prom=prom/i;
            return prom;
        }
        else
            return 0;
    }

    public float getCosto(){
        return getPromedio(filtrarData(getImporteExtraProm()),2);
    }

    public float getCostoOrden(){
        return getPromedio(filtrarData(getImporteExtraProm()),3);
    }


}
